package globox_server;

import java.io.EOFException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import utils.IGlobox;
import utils.Purchase;
import utils.Reservation;

public class GloboxServer implements IGlobox {

	/**
	 * this file defines the properties used in the program : port for
	 * connections with clients, timeout for reservation validation, etc
	 */
	private String configFile = "config/globox.properties";

	private String portClient = null;

	// defalut 10 seconds, but value may be set in config file
	private int reservCheckTimeout = 10;

	private ServerSocket ss;

	public boolean isCommStartClient;

	private Thread newServerThreadClient;

	private Map<String, Purchase> allPurchases = new HashMap<String, Purchase>();
	private Map<String, Reservation> allReservations = new HashMap<String, Reservation>();

	protected GloboxServer(boolean login) {
		//
		try {
			Properties p = new Properties();
			Reader reader = new FileReader(configFile);

			p.load(reader);
			// initialize fields
			portClient = p.getProperty("port");
			reservCheckTimeout = Integer.parseInt(p
					.getProperty("reservationValidationTimeout"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * this constuctor does the init of all fields authentication with clients
	 * 
	 */
	public GloboxServer() {

		try {
			newServerThreadClient = new EchoServiceClient(
					Integer.parseInt(portClient));
		} catch (Exception e) {
			System.err.println("Couldn't start TCP echo service for client: "
					+ e);
		}
	}

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws Exception {

		new GloboxServer(true).login();

		GloboxServer ep = new GloboxServer();
		ep.startServer();
	}

	/**
	 * verify if the user entered the right password
	 */
	public void login() throws IOException {
	}

	/** metodo para arrancar o servidor,usando threads para multiplas conexoes */
	public void startServer() {
		// loadVotesFromFile();
		try {
			newServerThreadClient.start();
		} catch (Exception e) {
			System.out.println("Couldn't start server. Reason: " + e);
		}
		return;
	}

	private static void usage(String[] args) {
		// TODO Auto-generated method stub
		if (args.length != 1) {
			System.out.printf("Usage : java ElectionServer <configfile> \n\n");
			System.exit(1);
		}

	}

	class EchoServiceClient extends Thread {

		public EchoServiceClient(int port) throws IOException {
			// TODO Auto-generated constructor stub
			super("Client Service");
			ss = new ServerSocket(port);
		}

		public void run() {
			Socket cs;
			while (true) {
				try {
					cs = ss.accept();
					(new ServerThread(cs)).start();
				} catch (IOException ie) {
					break;
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

	class ServerThread extends Thread {

		private static final String ADD_RESERVATION = "1";

		private static final String REMOVE_RESERVATION = "2";

		private static final String LIST_RESERVATIONS = "0";

		private static final String UPDATE_RESERVATION = "3";

		private static final String LIST_PURCHASES = "4";

		private static final String ADD_PURCHASE = "5";

		private static final String OK = "ok";

		private static final String NOK = "nok";

		private static final String LOGIN = "test";

		private static final String ERR_MSG = "I DON'T UNTERSTAND YOU";

		private Socket socket = null;

		private ObjectInputStream ois;

		private ObjectOutputStream oos;

		String[] toRcv = null;

		String stuffFromClient;

		private String portClient;

		private boolean IsClientLogged = false;

		ServerThread(Socket inSoc) {

			socket = inSoc;
			this.portClient = GloboxServer.this.portClient;
		}

		public void run() {
			try {
				ois = new ObjectInputStream(socket.getInputStream());
				oos = new ObjectOutputStream(socket.getOutputStream());

				for (;;) {
					// wait for message
					try {
						readWriteFromSocket();
					} catch (EOFException e) {
						System.out.println("Client disconnected");
						try {
							socket.close();
						} catch (Exception ex) {
						}
						break;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						try {
							socket.close();
						} catch (Exception ex) {
						}
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						try {
							socket.close();
						} catch (Exception ex) {
						}
					}
				}// FOR INFINITO
			} catch (SocketException se) {
				System.out.println(se.getMessage());
				try {
					socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} catch (EOFException e) {
				return;
			} catch (IOException e) {
				e.printStackTrace();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				try {
					ois.close();
					oos.close();
					// inStreamManager.close();
					// outStreamManager.close();
					socket.close();
					// manSock.close();
				} catch (IOException e) {

				}
			}
		}

		/**
		 * waiting for a message from client,rcv and treat it right
		 * 
		 * @throws ClassNotFoundException
		 */
		public synchronized void readWriteFromSocket() throws IOException,
				ClassNotFoundException {
			String stuffFromClient = "";
			// TODO Auto-generated method stub
			if (socket.getLocalPort() == Integer.parseInt(this.portClient)
					&& socket.isConnected()) {
				if (!IsClientLogged) {
					String loginMsg = (String) ois.readObject();

					if (loginMsg.equalsIgnoreCase(LOGIN)) {
						oos.writeObject(OK);
						oos.flush();
						IsClientLogged = true;
					} else {
						oos.writeObject(NOK);
						oos.flush();
						IsClientLogged = false;
					}
				}
				stuffFromClient = (String) ois.readObject();

				if (IsClientLogged) {
					if (stuffFromClient.equals(ADD_RESERVATION)) {
						try {
							Reservation res = (Reservation) ois.readObject();
							synchronized (GloboxServer.this) {
								addReservation(res);
							}
							oos.writeObject(OK);
							oos.flush();
						} catch (Exception e) {
							oos.writeObject(NOK);
							oos.flush();
						}
					} else if (stuffFromClient.equals(REMOVE_RESERVATION)) {
						try {
							UUID key = (UUID) ois.readObject();
							synchronized (GloboxServer.this) {
								removeReservation(key);
							}
							oos.writeObject(OK);
							oos.flush();
						} catch (Exception e) {
							oos.writeObject(NOK);
							oos.flush();
						}
					} else if (stuffFromClient.equals(LIST_RESERVATIONS)) {
						try {
							// System.out.println("cur alias:" + currentAlias);
							synchronized (GloboxServer.this) {
								List<Reservation> reservs = listReservations();
								oos.writeObject(reservs);
								oos.flush();
							}
						} catch (Exception e) {
							oos.writeObject(NOK);
							oos.flush();
						}
					} else if (stuffFromClient.equals(UPDATE_RESERVATION)) {
						try {
							UUID key = (UUID) ois.readObject();
							Reservation res = (Reservation) ois.readObject();
							synchronized (GloboxServer.this) {
								updateReservation(key, res);
							}
							oos.writeObject(OK);
							oos.flush();
						} catch (Exception e) {
							oos.writeObject(NOK);
							oos.flush();
						}
					} else {
						oos.writeObject(ERR_MSG);
						oos.flush();
						return;
					}
				}
			}
		}
	}

	@Override
	public void updateReservation(UUID key, Reservation r) throws Exception {
		allReservations.put(key.toString(), r);
	}

	@Override
	public List<Purchase> listPurchases() {
		List<Purchase> purchases = new ArrayList<Purchase>();
		for (Purchase ev : allPurchases.values())
			purchases.add(ev);
		return purchases;
	}

	@Override
	public List<Reservation> listReservations() {
		// TODO Auto-generated method stub
		List<Reservation> reservation = new ArrayList<Reservation>();
		for (Reservation ev : allReservations.values())
			reservation.add(ev);
		return reservation;
	}

	@Override
	public void addPurchase(Purchase p) {
		UUID key = null;
		while (!allPurchases.containsKey(key)) {
			key = UUID.randomUUID();
			allPurchases.put(key.toString(), p);
		}
		// set id for added purchase
		p.setId(key);
	}

	@Override
	public void removePurchase(UUID key) {
		allPurchases.remove(key);
	}

	@Override
	public void addReservation(Reservation r) {
		//
		UUID key = null;
		while (!allReservations.containsKey(key)) {
			key = UUID.randomUUID();
			allReservations.put(key.toString(), r);
		}
	}

	@Override
	public void removeReservation(UUID key) {
		//
		allReservations.remove(key);
	}
}
